package com.yst.webplugin.additional;


import com.yst.web.utils.BeanUtils;
import com.yst.web.utils.ClassUtils;
import com.yst.webplugin.additional.mask.FieldMaskConfigItem;
import com.yst.webplugin.additional.mask.Mask;
import com.yst.webplugin.additional.transform.*;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Transform for result
 */
public class ResultProcessor {

    private final Map<String, TransformConfigItem> transformConfigItemMap=new Hashtable<>();
    private final Map<String, FieldMaskConfigItem> fieldMaskConfigItemMap=new Hashtable<>();

    private final List<String> disableFieldList=new ArrayList<>();

    /**
     * Append transform config
     * @param transformConfigItems item of transform item
     */
    public ResultProcessor addConfig(TransformConfigItem...transformConfigItems){
        for(TransformConfigItem transformConfigItem:transformConfigItems){
            transformConfigItemMap.put(transformConfigItem.getNameField(),transformConfigItem);
        }
        return this;
    }

    public ResultProcessor addAllTransformConfig(Collection<? extends TransformConfigItem> configItems){
        addConfig(configItems.toArray(new TransformConfigItem[]{}));
        return this;
    }

    public ResultProcessor addAllFieldMaskConfig(Collection<? extends FieldMaskConfigItem> configItems){
        addConfig(configItems.toArray(new FieldMaskConfigItem[]{}));
        return this;
    }

    public ResultProcessor resetDisableField(){
        disableFieldList.clear();
        return this;
    }

    public ResultProcessor disableField(String...fields){
        disableFieldList.addAll(Arrays.asList(fields));
        return this;
    }

    public ResultProcessor disableField(Collection<String> fields){
        disableFieldList.addAll(fields);
        return this;
    }

    /**
     * Append config for mask
     * @param fieldMaskConfigItems config for mask
     * @return
     */
    public ResultProcessor addConfig(FieldMaskConfigItem...fieldMaskConfigItems){
        for(FieldMaskConfigItem fieldMaskConfigItem:fieldMaskConfigItems){
            fieldMaskConfigItemMap.put(fieldMaskConfigItem.getFieldName(),fieldMaskConfigItem);
        }
        return this;
    }

    /**
     * process transform and mask
     * @param source source object
     * @param fieldMap map for field
     */
    public void process(Object source, Map<String,Field> fieldMap){
        if (Objects.isNull(fieldMap)||Objects.isNull(source)) return;
        transformConfigItemMap.keySet().forEach((fieldName)->{
            TransformConfigItem config=transformConfigItemMap.get(fieldName);
            Map<Object, DictionaryItem> dict= TransDictUtil
                    .getDictionaries()
                    .get(config.getDictionaries());

            if (Objects.isNull(dict)) {
                throw new RuntimeException(String.format("not find dictionary %s",config.getDictionaries()));
            }

            if (fieldMap.containsKey(fieldName)){
                Object idValue= BeanUtils.getFieldValue(source,fieldName,null);
                if (Objects.isNull(idValue)) return;
                if (dict.containsKey(idValue))
                {
                    DictionaryItem item=dict.get(idValue);
                    try {
                        BeanUtils.setFieldValue(config.getNameField(), source, item.getValue());
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });

        fieldMaskConfigItemMap.keySet().forEach((fieldName)->{
            FieldMaskConfigItem fieldMaskConfigItem=fieldMaskConfigItemMap.get(fieldName);
            if (fieldMap.containsKey(fieldName)){
                Object value=BeanUtils.getFieldValue(source,fieldName,null);
                value=fieldMaskConfigItem.getMaskInterface().make((String) value);
                try {
                    BeanUtils.setFieldValue(fieldName,source,value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        disableFieldList.forEach((fieldName)->{
            try {
                BeanUtils.setFieldValue(fieldName,source,null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    public void processMap(Map<Object,Object> values){
        if (Objects.isNull(values)) return;
        transformConfigItemMap.keySet().forEach((fieldName)->{
            TransformConfigItem config=transformConfigItemMap.get(fieldName);
            Map<Object, DictionaryItem> dict= TransDictUtil
                    .getDictionaries()
                    .get(config.getDictionaries());

            if (Objects.isNull(dict)) {
                throw new RuntimeException(String.format("not find dictionary %s",config.getDictionaries()));
            }

            if (values.containsKey(fieldName)){
                Object idValue= values.get(fieldName);
                if (Objects.isNull(idValue)) return;
                if (dict.containsKey(idValue))
                {
                    DictionaryItem item=dict.get(idValue);
                    try {
                        values.put(config.getNameField(),item.getValue());
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });

        fieldMaskConfigItemMap.keySet().forEach((fieldName)->{
            FieldMaskConfigItem fieldMaskConfigItem=fieldMaskConfigItemMap.get(fieldName);
            if (values.containsKey(fieldName)){
                Object value=values.get(fieldName);
                value=fieldMaskConfigItem.getMaskInterface().make((String) value);
                try {
                    values.put(fieldName,value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        disableFieldList.forEach(values::remove);
    }

    /**
     * process transform and mask
     * @param source source object
     */
    public void process(Object source){
        if (Objects.isNull(source)) return;
        Map<String, Field> fieldMap= ClassUtils.getClassFields(source.getClass());
        process(source,fieldMap);
    }

    /**
     * process transform and mask
     * @param sources collection for source
     */
    public void processCollection(Collection<?> sources){
        if (sources.isEmpty()) return;
        Class<?> clazz=sources.stream().findFirst().get().getClass();
        if (ClassUtils.isAssignableFrom(clazz, Map.class)) {
            for(Object data:sources){
                processMap((Map)data);
            }
            return;
        }
        Map<String, Field> fieldMap=ClassUtils.getClassFields(clazz);
        for(Object data:sources){
            process(data,fieldMap);
        }
    }

    private boolean fieldNeedProcess(Field field){
        return Objects.isNull(field.getAnnotation(Mask.class))
                &&Objects.isNull(field.getAnnotation(Transform.class));
    }
    /**
     * Deep process result, supports {@link Transform annotation for class or field}
     * @param source object need to be processed
     * @param elementClass specific class need to be processed
     */
    public void deepProcess(Object source, Class<?>...elementClass){
        if (Objects.isNull(source)) return;
        if (ClassUtils.isAssignableFrom(source.getClass(), Collection.class)){
            for(Object obj:(Collection)source){
                deepProcess(obj,elementClass);
            }
            return;
        }
        Map<String,Field> fieldMap=ClassUtils.getClassFields(source.getClass());
        if (Objects.isNull(fieldMap)) return;
        if (ClassUtils.isAssignableFrom(source.getClass(),elementClass)){
            process(source,fieldMap);
            return;
        }

        addAllTransformConfig(TransformConfigUtil.fromClass(source.getClass()));
        fieldMap.values().forEach((field)->{

            if (Modifier.isFinal(field.getModifiers())
                    ||Modifier.isStatic(field.getModifiers())
                    ||Modifier.isTransient(field.getModifiers())){
                return;
            }

            addAllTransformConfig(TransformConfigUtil.fromField(field));

            TransformConfigItem transformConfigItem=transformConfigItemMap.get(field.getName());
            if (Objects.nonNull(transformConfigItem)){
                if (Objects.nonNull(TransDictUtil.getDictionary(transformConfigItem.getDictionaries()))){
                    Object idValue=BeanUtils.getFieldValue(source,transformConfigItem.getIdField(),null);
                    if (Objects.nonNull(idValue)) {
                        DictionaryItem dictionaryItem = TransDictUtil.getDictionary(transformConfigItem.getDictionaries()).get(idValue);
                        try {
                            if (Objects.nonNull(dictionaryItem)) {
                                Object value = dictionaryItem.getValue();
                                String nameField = transformConfigItem.getNameField();
                                BeanUtils.setFieldValue(nameField, source, value);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
                else {
                    throw new RuntimeException(String.format("Can't find dictionary %s",transformConfigItem.getDictionaries()));
                }
            }

            Object value=BeanUtils.getFieldValue(source,field.getName(),null);
            if (Objects.isNull(value)) return;

            if (value instanceof String){
                FieldMaskConfigItem maskConfigItem=fieldMaskConfigItemMap.get(field.getName());
                if (Objects.nonNull(maskConfigItem)) {
                    value = maskConfigItem.getMaskInterface().make((String) value);
                    try {
                        BeanUtils.setFieldValue(field.getName(), source, value);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                return;
            }

            if (ClassUtils.isPrimitive(field.getType())){
                return;
            }

            if (ClassUtils.isAssignableFrom(value.getClass(),elementClass)){
                process(value);
                return;
            }

            if (ClassUtils.isAssignableFrom(value.getClass(), Collection.class)){
                Collection<?> collection=(Collection<?>)value;
                if (!collection.isEmpty())
                {
                    if (ClassUtils.isAssignableFrom(collection.stream().findFirst()
                            .get().getClass(),elementClass)){
                        processCollection(collection);
                    }
                    else {
                        collection.forEach((el)->{
                            deepProcess(el,elementClass);
                        });
                    }
                }
                return;
            }
            if (ClassUtils.isAssignableFrom(value.getClass(), Map.class)){
                processMap((Map)value);
                return;
            }
            if (field.getType().isArray()){
                Object values=BeanUtils.getFieldValue(source,field.getName(),null);
                if (Objects.nonNull(values)){
                    try {
                        Object[] array = (Object[]) values;
                        if (array.length > 0 && ClassUtils.isAssignableFrom(array[0].getClass(), elementClass)) {
                            try {
                                processCollection(Arrays.asList(array));
                            }
                            catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }
                }
                return;
            }
            deepProcess(value,elementClass);
        });
    }
}
